package com.lap.web.starter.core.advice;

import com.lap.framework.common.annotation.ResultSkip;
import com.lap.framework.common.dto.result.Result;
import com.lap.framework.common.tool.JsonUtil;
import jakarta.annotation.PostConstruct;
import java.lang.reflect.Method;
import java.util.Set;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

/**
 * 返回结果统一处理:
 *
 * <p>可以在返回方法中添加 {@link ResultSkip} 忽略返回增强，该类不实用统一返回结果.
 *
 * <p>初始化参考自动装载类 {@link com.lap.web.starter.autoconfigure.WebAutoConfiguration}
 *
 * @author Shuisheng Lao(劳水生)
 * @version 0.0.1
 * @see ResultSkip
 */
@Slf4j
@RestControllerAdvice(basePackages = "com.lap")
public class RestBodyAdvice implements ResponseBodyAdvice<Object> {

  private static final Set<MediaType> BINARY_MEDIA_TYPES =
      Set.of(MediaType.IMAGE_JPEG, MediaType.IMAGE_PNG, MediaType.APPLICATION_PDF);

  @PostConstruct
  public void init() {
    log.info("Initializer web rest body advice.");
  }

  @Override
  public boolean supports(
      MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
    Method returnTypeMethod = returnType.getMethod();
    assert returnTypeMethod != null;
    return !returnTypeMethod.isAnnotationPresent(ResultSkip.class);
  }

  @Override
  public Object beforeBodyWrite(
      Object body,
      @NonNull MethodParameter returnType,
      @NonNull MediaType selectedContentType,
      @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
      @NonNull ServerHttpRequest request,
      @NonNull ServerHttpResponse response) {
    if (skipType(selectedContentType, body)) {
      return body;
    }

    if (body instanceof String) { // return string is error
      return JsonUtil.toJson(Result.ok(body));
    }
    return Result.ok(body);
  }

  private boolean skipType(MediaType contentType, Object body) {
    return BINARY_MEDIA_TYPES.stream().anyMatch(contentType::includes) || body instanceof Result;
  }
}
